
package de.uni.wuppertal.server;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import de.uni.wuppertal.server.ftp.FTPServer;
import de.uni.wuppertal.server.http.HTTPServer;

/**
 * @author Martin Schröder
 * @author Patrik Nordsiek
 */
public class ServerManager
{
	public static final String TYPE_FTP = "ftp";
	public static final String TYPE_HTTP = "http";
	
	private static final String NS = "http://uni-wuppertal.de/xmlns/2011/server";
	
	private Map<Integer, ServerInfo> servers = new HashMap<Integer, ServerInfo>();
	private Map<ServerInfo, Thread> serverThreads = new HashMap<ServerInfo, Thread>();
	
	public synchronized boolean isPortInUse(int port)
	{
		return servers.containsKey(port);
	}
	
	public synchronized void addServer(ServerInfo info) throws Throwable
	{
		Integer port = info.getPort();
		
		if(servers.containsKey(port))
		{
			throw new RuntimeException("Server port already in use: " + port);
		}
		
		servers.put(port, info);
	}
	
	public synchronized void startServer(ServerInfo server) throws Throwable
	{
		if(!serverThreads.containsKey(server))
		{
			Thread thread = new Thread(server);
			thread.setDaemon(true);
			
			serverThreads.put(server, thread);
			
			thread.start();
		}
	}
	
	public synchronized void shutdownServer(ServerInfo server) throws Throwable
	{
		if(serverThreads.containsKey(server))
		{
			Thread serverThread = serverThreads.get(server);
			serverThreads.remove(server);
			
			server.stopServer();
			
			serverThread.join();
		}
	}
	
	public synchronized boolean isRunning(ServerInfo server)
	{
		return serverThreads.containsKey(server);
	}
	
	public synchronized ServerInfo[] getServers()
	{
		return servers.values().toArray(new ServerInfo[] {});
	}
	
	public synchronized void shutdownServers() throws Throwable
	{
		for(ServerInfo info : servers.values())
		{
			shutdownServer(info);
		}
	}
	
	public synchronized void newConfiguration() throws Throwable
	{
		shutdownServers();
		
		servers.clear();
		serverThreads.clear();
	}
	
	public synchronized void removeServer(ServerInfo info) throws Throwable
	{
		shutdownServer(info);
		
		if(servers.containsKey(info.getPort()))
		{
			servers.remove(info.getPort());
		}
	}
	
	public synchronized void loadConfigurationFile(File file) throws Throwable
	{
		shutdownServers();
		
		servers.clear();
		serverThreads.clear();
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);
		Document doc = factory.newDocumentBuilder().parse(file);
		
		GenericNamespaceContext context = new GenericNamespaceContext();
		context.registerNamespace("s", NS);
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		xpath.setNamespaceContext(context);
		
		NodeList serverNodes = (NodeList)xpath.evaluate("/s:configuration/s:server", doc, XPathConstants.NODESET);
		
		for(int count = serverNodes.getLength(), i = 0; i < count; i++)
		{
			Element serverElement = (Element)serverNodes.item(i);
			
			String name = serverElement.getAttribute("name");
			String type = serverElement.getAttribute("type");
			String root = serverElement.getAttribute("root");
			
			int port = Integer.valueOf(serverElement.getAttribute("port"));
			
			if(type.equals(TYPE_HTTP))
			{
				HTTPServer server = new HTTPServer();
				server.setName(name);
				server.setPort(port);
				server.setRootDirectory(root);
				
				addServer(server);
			}
			else
			{
				FTPServer server = new FTPServer();
				server.setName(name);
				server.setPort(port);
				server.setRootDirectory(root);
				
				addServer(server);
			}
		}
	}
	
	public synchronized void saveConfigurationFile(File file) throws Throwable
	{
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);
		Document doc = factory.newDocumentBuilder().newDocument();
		Element rootElement = (Element)doc.appendChild(doc.createElementNS(NS, "configuration"));
		
		for(ServerInfo info : servers.values())
		{
			Element serverElement = (Element)rootElement.appendChild(doc.createElementNS(NS, "server"));
			
			if(info instanceof HTTPServer)
			{
				serverElement.setAttribute("type", TYPE_HTTP);
			}
			else
			{
				serverElement.setAttribute("type", TYPE_FTP);
			}
			
			serverElement.setAttribute("name", info.getName());
			serverElement.setAttribute("port", String.valueOf(info.getPort()));
			serverElement.setAttribute("root", info.getRootDirectory());
		}
		
		Transformer transformer = TransformerFactory.newInstance().newTransformer();
		transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		
		transformer.transform(new DOMSource(doc), new StreamResult(file));
	}
}
